package it.rainbowbreeze.eureka.ui;

import it.rainbowbreeze.eureka.BuildConfig;
import it.rainbowbreeze.eureka.R;
import it.rainbowbreeze.eureka.common.AppEnv;
import it.rainbowbreeze.eureka.common.LogFacility;
import it.rainbowbreeze.eureka.data.AppPreferencesDao;
import it.rainbowbreeze.eureka.domain.FilterCategories;
import it.rainbowbreeze.eureka.domain.QualityDataPoint;
import it.rainbowbreeze.eureka.logic.FiltersManager;
import it.rainbowbreeze.eureka.logic.QualityDataPointManager;
import it.rainbowbreeze.eureka.logic.QualityDataPointManager.DataAvailableListener;
import it.rainbowbreeze.eureka.ui.view.QualityItemizedOverlay;
import it.rainbowbreeze.eureka.ui.view.QualityOverlayItem;
import it.rainbowbreeze.eureka.ui.view.RainbowMapView;

import java.util.List;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.littlefluffytoys.littlefluffylocationlibrary.LocationInfo;
import com.littlefluffytoys.littlefluffylocationlibrary.LocationLibrary;
import com.littlefluffytoys.littlefluffylocationlibrary.LocationLibraryConstants;

public class MainMapActivity extends MapActivity {
    // ------------------------------------------ Private Fields
    private static final String LOG_HASH = MainMapActivity.class.getSimpleName();

    private static final int REQCODE_FILTERS = 100;

    private LogFacility mLogFacility;
    private AppPreferencesDao mAppPreferencesDao;
    private QualityDataPointManager mQualityDataPointManager;
    private ActivityHelper mActivityHelper;
    private FiltersManager mFiltersManager;
    
    private RainbowMapView mMapView;
    private CheckBox mChkFilterEconomics;
    private CheckBox mChkFilterEnvironment;
    private CheckBox mChkFilterSocial;
    private ProgressBar mProgressBar;
    private int mProgressBarDisplayCounter;
    
    /** Contains the points shown in the map */
    private QualityItemizedOverlay mItemizedOverlay;
    /** Stores current data retrieval job keys, based on latitude longitude and zoom level. */
    private String mCurrentDataRetrievalJobKey;

    // -------------------------------------------- Constructors
    
    // --------------------------------------- Public Properties
    
    // -------------------------------------------------- Events

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        AppEnv appEnv = AppEnv.i(getApplicationContext());
        mLogFacility = appEnv.getLogFacility();
        mLogFacility.logStartOfActivity(getClass(), savedInstanceState);
        mAppPreferencesDao = appEnv.getAppPreferencesDao();
        mQualityDataPointManager = appEnv.getQualityDataPointManager();
        mActivityHelper = appEnv.getActivityHelper();
        mFiltersManager = appEnv.getFiltersManager();
        
        setContentView(R.layout.actmainmap);
        String apiKey;
        if (BuildConfig.DEBUG) {
            apiKey = "0uyOkTKmM6LwEHCs_Kgt4zfiE4Da_8UBOIsfglA";
        } else {
            apiKey = "0uyOkTKmM6Lz2tsPhShe8FG1fQ-MHWxD8FtiSQA";
        }
        mLogFacility.e("Api key: " + apiKey);
        mMapView = new RainbowMapView(this, apiKey);
        mMapView.setClickable(true);
        mMapView.setBuiltInZoomControls(true);
        setMapPosition(
                mMapView,
                mAppPreferencesDao.getLastLatitude(),
                mAppPreferencesDao.getLastLongitude(),
                mAppPreferencesDao.getLastZoom());
        mMapView.setNotifyChangesOnlyWhenCompled(true);
        mMapView.setOnChangeListener(mMapOnChangeListener, true);
        //add map to the activity
        ViewGroup mapContainer = (ViewGroup) findViewById(R.id.mainmap_layMapView);
        mapContainer.addView(mMapView);
        Drawable drawable = this.getResources().getDrawable(R.drawable.androidmarker);
        mItemizedOverlay = new QualityItemizedOverlay(mLogFacility, drawable, this);
        //bound map with custom overlays
        List<Overlay> mapOverlays = mMapView.getOverlays();
        mapOverlays.add(mItemizedOverlay);
        
        ImageButton button;
        button = (ImageButton) findViewById(R.id.tabbar_btnInfo);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mActivityHelper.openAbout(MainMapActivity.this);
            }
        });
        button = (ImageButton) findViewById(R.id.tabbar_btnImport);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mActivityHelper.openImport(MainMapActivity.this);
            }
        });
        button = (ImageButton) findViewById(R.id.tabbar_btnFilters);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mActivityHelper.openFilters(MainMapActivity.this, REQCODE_FILTERS);
            }
        });
        button = (ImageButton) findViewById(R.id.mainmap_btnPosition);
        button.setOnClickListener(mBtnGetLocationClickListener);
        
        mProgressBar = (ProgressBar) findViewById(R.id.mainmap_progress);
        mProgressBar.setVisibility(View.GONE);
        mProgressBarDisplayCounter = 0;
        
        //restore filter categories status
        mChkFilterEconomics = (CheckBox) findViewById(R.id.navbar_btnEconomic);
        setFilterButton(mChkFilterEconomics, FilterCategories.Economics);
        mChkFilterEnvironment = (CheckBox) findViewById(R.id.navbar_btnEnvironment);
        setFilterButton(mChkFilterEnvironment, FilterCategories.Environment);
        mChkFilterSocial = (CheckBox) findViewById(R.id.navbar_btnSocial);
        setFilterButton(mChkFilterSocial, FilterCategories.Social);
    }

    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }
    
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQCODE_FILTERS:
            launchMapDataPointsUpdate(mMapView);
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
        }

    }

    @Override
    protected void onPause() {
        super.onPause();
        
        //categories are saved on every button press
        //save latest coordinates
        GeoPoint center = mMapView.getMapCenter();
        mAppPreferencesDao.setLastLatitude(center.getLatitudeE6());
        mAppPreferencesDao.setLastLongitude(center.getLongitudeE6());
        mAppPreferencesDao.setLastZoom(mMapView.getZoomLevel());
        mAppPreferencesDao.save();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //unregistered here instead of onPause because data loading
        //can continue also when other activities are in foreground
        mQualityDataPointManager.unregisterDataAvailableListener();
        if (null != mLocationReceiver) unregisterReceiver(mLocationReceiver);
        mLocationReceiver = null;
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        mQualityDataPointManager.registerDataAvailableListener(mDataAvailableListener);
    }
    
    
    // ------------------------------------------ Public Methods

    // ----------------------------------------- Private Methods
    private void setMapPositionFromIntent(MapView mapView, Intent intent) {
        int latitude;
        int longitude;
        int zoom;
        if (null == intent || null == intent.getExtras()) {
            latitude = mAppPreferencesDao.getLastLatitude();
            longitude = mAppPreferencesDao.getLastLongitude();
            zoom = mAppPreferencesDao.getLastZoom();
        } else {
            //default values
            latitude = intent.getIntExtra(ActivityHelper.KEY_LATITUDE, 0);
            longitude = intent.getIntExtra(ActivityHelper.KEY_LONGITUDE, 0);
            zoom = 13;
        }
        mLogFacility.v(LOG_HASH, "Setting position to lat:" + latitude + ", lon:" + longitude + ", zoom:" + zoom);
        setMapPosition(mapView, latitude, longitude, zoom);
    }

    private void setMapPosition(MapView mapView, int latitude, int longitude, int zoom) {
        GeoPoint startingPoint = new GeoPoint(latitude, longitude);
        mapView.getController().animateTo(startingPoint);
        mapView.getController().setZoom(zoom);
    }

    /**
     * Updates quality data point on the map
     * @param resultData
     */
    protected void updateMapDataPoints(final List<QualityDataPoint> resultData) {
        this.runOnUiThread(new Runnable() {
            public void run() {
                //add new data
                if (null != resultData) {
                    mLogFacility.v(LOG_HASH, "Adding " + resultData.size() + " new points");
                    //remove old data
                    mItemizedOverlay.clear();
                    int total =  0;
                    for (QualityDataPoint dataPoint : resultData) {
                        //TODO find a more elegant way to exit from the loop
                        if (++total > 150) break;
                        GeoPoint point = new GeoPoint(dataPoint.getLatitude(), dataPoint.getLongitude());
                        QualityOverlayItem overlayItem = new QualityOverlayItem(
                                point,
                                getString(R.string.mainmap_dataPointTitle),
                                String.format(
                                        getString(R.string.mainmap_dataPointMessage),
                                        dataPoint.getLatitude(),
                                        dataPoint.getLongitude(),
                                        dataPoint.getScore() + 1), //score is from 0 to 4
                                dataPoint.getScore(),
                                dataPoint.getRadius());

                        mItemizedOverlay.addOverlay(overlayItem);
                        
                    }
                    //and redraw the map
                    mMapView.invalidate();
                } else {
                    mLogFacility.v(LOG_HASH, "No new datapoints to show");
                }
                hideProgressIndicator();
            }
        });
    }

    /**
     * Listener used to check map data
     */
    private DataAvailableListener mDataAvailableListener = new QualityDataPointManager.DataAvailableListener() {
        public void dataIsAvailable(String searchKey, List<QualityDataPoint> resultData) {
            //checks if the data retrieved are related to current position / zoom level
            if (!TextUtils.isEmpty(searchKey) && searchKey.equals(mCurrentDataRetrievalJobKey)
                    || TextUtils.isEmpty(mCurrentDataRetrievalJobKey)) {
                mLogFacility.v(LOG_HASH, "Data points updating...");
                updateMapDataPoints(resultData);
            } else {
                hideProgressIndicator();
            }
        }
    };
    
    private RainbowMapView.OnChangeListener mMapOnChangeListener = new RainbowMapView.OnChangeListener() {
        public void onChange(RainbowMapView view, GeoPoint newCenter, GeoPoint oldCenter, int newZoom, int oldZoom) {
            launchMapDataPointsUpdate(view);
        }
    };

    private OnCheckedChangeListener mOnCategoryCheckedChangeListerner = new CompoundButton.OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            //re-finds selected categories
            mFiltersManager.saveCategoriesStatuses(
                    mChkFilterEconomics.isChecked(),
                    mChkFilterEnvironment.isChecked(),
                    mChkFilterSocial.isChecked());
            //reload data
            launchMapDataPointsUpdate(mMapView);
        }
    };
    
    protected BroadcastReceiver mLocationReceiver;

    private void setFilterButton(CheckBox button, FilterCategories category) {
        button.setChecked(mFiltersManager.isFilterCategoryActive(category));
        button.setOnCheckedChangeListener(mOnCategoryCheckedChangeListerner);
        
    }

    /**
     * Updates point on map if necessary
     * @param mapView
     */
    protected void launchMapDataPointsUpdate(RainbowMapView mapView) {
        if (!mFiltersManager.filtersArePresent()) {
            mLogFacility.i(LOG_HASH, "No new data to load, filters remove all data");
            //remove old data
            mItemizedOverlay.clear();
            mMapView.invalidate();
            return;
        }
        showProgressIndicator();
        GeoPoint newCenter = mapView.getMapCenter();
        int newZoom = mapView.getZoomLevel();
        mLogFacility.i(LOG_HASH, "Zoom: " + newZoom + ", center: " + newCenter.toString());
        int latitudeSpan = mapView.getLatitudeSpan();
        int longitudeSpan = mapView.getLongitudeSpan();
        mCurrentDataRetrievalJobKey = mQualityDataPointManager.getDataPointsForGivenBoudaries(
                getApplicationContext(),
                newCenter,
                latitudeSpan,
                longitudeSpan,
                newZoom,
                mFiltersManager.getActiveMeasureIdsFilters());
        
        //int[][] bounds = view.getBounds();
        //mLogFacility.i(LOG_HASH, "Bounds: " + bounds[0][0] + ", " + bounds[0][1] + ", " + bounds[1][0] + ", " + bounds[1][1]);
        //if (minLat > bounds[0][0]) minLat = bounds[0][0];
        //if (minLng > bounds[0][1]) minLng = bounds[0][1];
        //if (maxLat < bounds[1][0]) maxLat = bounds[1][0];
        //if (maxLng < bounds[1][1]) maxLng = bounds[1][1];
        //LAT +-90; long +-180
        //I/Eureka  ( 6012): [ActMap] minLat: -153627788, minLng: -517500000, maxLat: 160000000, maxLng: 517500000
        //mLogFacility.i(LOG_HASH, "minLat: " + minLat + ", minLng: " + minLng + ", maxLat: " + maxLat + ", maxLng: " + maxLng);
    }

    private void setTestOverlay() {
        GeoPoint point = new GeoPoint(19240000,-99120000);
        QualityOverlayItem overlayitem = new QualityOverlayItem(
                point, "Hola, Mundo!", "I'm in Mexico City!", 1, 100);
        GeoPoint point2 = new GeoPoint(35410000, 139460000);
        QualityOverlayItem overlayitem2 = new QualityOverlayItem(
                point2, "Sekai, konichiwa!", "I'm in Japan!", 2, 150);
        
        mItemizedOverlay.addOverlay(overlayitem);
        mItemizedOverlay.addOverlay(overlayitem2);
    }
    
    private void showProgressIndicator() {
        if (mProgressBarDisplayCounter <= 0) {
            mProgressBarDisplayCounter = 0;
            this.runOnUiThread(new Runnable() {
                public void run() {
                    mProgressBar.setVisibility(View.VISIBLE);
                }
            });
        }
        mProgressBarDisplayCounter++;
    }
    
    private void hideProgressIndicator() {
        mProgressBarDisplayCounter--;
        if (mProgressBarDisplayCounter <= 0) {
            mProgressBarDisplayCounter = 0;
            this.runOnUiThread(new Runnable() {
                public void run() {
                    mProgressBar.setVisibility(View.GONE);
                }
            });
        }
    }
    
    private View.OnClickListener mBtnGetLocationClickListener = new View.OnClickListener() {
        public void onClick(View v) {
            final LocationManager manager = (LocationManager) getSystemService( Context.LOCATION_SERVICE );
            if (!manager.isProviderEnabled( LocationManager.GPS_PROVIDER )) {
                Toast.makeText(getBaseContext(), R.string.mainmap_msgNoGpsAvailable, Toast.LENGTH_SHORT).show();
                return;
            }
            
            if (null != mLocationReceiver) {
                mLogFacility.v(LOG_HASH, "Location listener already registered");
                return;
            }
            
            //register the listener
            mLocationReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    mLogFacility.v(LOG_HASH, "onReceive: received location update");
                    getBaseContext().unregisterReceiver(mLocationReceiver);
                    mLocationReceiver = null;
                    final LocationInfo locationInfo = (LocationInfo) intent.getSerializableExtra(LocationLibraryConstants.LOCATION_BROADCAST_EXTRA_LOCATIONINFO);
                    int latitude = (int) (locationInfo.lastLat * 1E6);
                    int longitude = (int) (locationInfo.lastLong * 1E6);
                    setMapPosition(mMapView, latitude, longitude, 12);
                }
            };

            try {
                mLogFacility.v(LOG_HASH, "Asking for a new location");
                IntentFilter filter = new IntentFilter(LocationLibraryConstants.LOCATION_CHANGED_TICKER_BROADCAST_ACTION);
                getBaseContext().registerReceiver(mLocationReceiver, filter);
                LocationLibrary.forceLocationUpdate(getBaseContext());
            } catch (Exception e) {
                Toast.makeText(getBaseContext(), R.string.mainmap_msgNoGpsAvailable, Toast.LENGTH_SHORT).show();
            }
        }
    };
    
    // ----------------------------------------- Private Classes

}